package com.ke.rule.base;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * validation exception
 * 
 * @author guyu
 *
 */
public class ValidationException extends BusinessException {

  private static final long serialVersionUID = 1L;

  public ValidationException(String code, String message, LinkedHashMap<String, Object> args) {
    super(code, message, args);
  }

  public ValidationException(String code, String message, Throwable cause) {
    super(code, message, cause);
  }

  public ValidationException(String code, String message) {
    super(code, message);
  }

  public ValidationException(Throwable cause) {
    super(cause);
  }

  /**
   * 校验异常构建器
   */
  public static class Builder implements Cloneable {
    private List<Problem> problems;
    
    private Builder() {
      super();
      this.problems = new ArrayList<Problem>();
    }
    
    /**
     * get instance
     * @return
     */
    public static Builder getInstance() {
      return new Builder();
    }
    
    @Override
    public Builder clone() {
      Builder clone = new Builder();
      this.problems.forEach(p -> clone.problems.add(p.clone()));
      
      return clone;
    }
    
    public List<Problem> getProblems() {
      return Collections.unmodifiableList(problems);
    }
    
    public Builder addProblem(Problem problem) {
      problems.add(problem);
      return this;
    }

    public Builder addProblem(List<Problem> problem) {
      problems.addAll(problem);
      return this;
    }

    public Builder addProblem(ProblemTypeEnum type, String code) {
      return addProblem("", type, code);
    }
    
    public Builder addProblem(String fieldPath, ProblemTypeEnum type, String code) {
      return addProblem(fieldPath, type, code, null);
    }
    

    public Builder addProblem(String fieldPath, ProblemTypeEnum type, String code, LinkedHashMap<String, Object> args) {
      return addProblem(new Problem(fieldPath, type, null, code, args));
    }

    public Builder removeProblem(String code, String fieldPath) {
      problems.removeIf(p -> code.equals(p.getCode()) && (null == fieldPath ? true : fieldPath.equals(p.getFieldPath())));
      return this;
    }
    
    public Builder merge(Builder builder) {
      builder.problems.forEach(p -> this.problems.add(p.clone()));
      return this;
    }

    /**
     * 构建
     *
     * 如果存在问题则抛出指定错误码的异常， 异常的parameter中有一个problems对象，结构为：
     *
     * <pre>
     *    {
     *        <group>: {
     *            <ProblemTypeEnum>: [problem list],
     *        }
     *    }
     * </pre>
     *
     * 其中对于没有group的，归到default groups
     *
     * @return
     */
    @SuppressWarnings("unchecked")
    public ValidationException build(String errorCode, String messgae) {
      if (!isPresent()) {
        return null;
      }

      Map<String, Map<ProblemTypeEnum, List<Problem>>> type2Problems = null;
      // 若problem有且仅有一个时，需要检查该problem是否仅仅用来包装其他的problem
      // 为了避免problem的无意义嵌套，需要获取root problem
      if (1 == problems.size()) {
        Problem wrapProblem = problems.get(0);
        if (null != wrapProblem.getArgs() && wrapProblem.getArgs().containsKey(ARG_KEY_PROBLEMS)) {
          type2Problems = (Map<String, Map<ProblemTypeEnum, List<Problem>>>) wrapProblem.getArgs().get(ARG_KEY_PROBLEMS);
        }
      }
      if (null == type2Problems) {
        type2Problems = BusinessExceptionUtils.groupByType(problems);
      }

      LinkedHashMap<String, Object> args = new LinkedHashMap<>();
      args.put(ARG_KEY_PROBLEMS, type2Problems);

      return new ValidationException(errorCode, messgae, args);
    }

    /**
     * is there no problem
     * @return
     */
    public boolean isNoProblem() {
      return CollectionUtils.isEmpty(problems);
    }
    
    /**
     * 是否存在异常？
     * @return
     */
    public boolean isPresent() {
      if (isNoProblem()) {
        return false;
      }

      return assertException(problems);
    }
    
    /**
     * 构建若存在异常则抛出
     */
    public void throwIfPresent() {
      //error级别的错误，用fieldPath-code拼接
      Set<String> errorLevelKeys = problems.stream().filter(p -> (ProblemTypeEnum.ERROR.equals(p.getType()) && StringUtils.isNotBlank(p.getFieldPath()) && StringUtils.isNotBlank(p.getCode())))
          .map(p -> String.format("%s-%s", p.getFieldPath(), p.getCode())).collect(Collectors.toSet());
      //停用检查部件改造：对于配置了setting的部件，根据从严策略，同一部件可能会执行多次，会导致错误码重复，此处根据fieldPath+code过滤，对于重复的problem，保留错误级别高的信息
      Iterator<Problem> it = problems.iterator();
      Set<String> keys = new HashSet<>();
      Problem problem = null;
      while (it.hasNext()) {
        problem = it.next();
        if (StringUtils.isNotBlank(problem.getFieldPath()) && StringUtils.isNotBlank(problem.getCode())) {
          String key = String.format("%s-%s", problem.getFieldPath(), problem.getCode());
          if(ProblemTypeEnum.WARNING.equals(problem.getType()) && errorLevelKeys.contains(key)) {
            it.remove();
            continue;
          }
          if (keys.contains(key)) {
            it.remove();
          }
          keys.add(key);
        }
      }
      
      String errorCodes = problems.stream().map(p -> p.getCode()).collect(Collectors.joining(", ", "[", "]"));
      ValidationException exception = build(ExceptionCodes.EXISTS_VALIDATION_RPOBLEM,
          String.format("Object validation found %d problems, error codes: %s", this.problems.size(), errorCodes));
      if (null != exception) {
        throw exception;
      }
    }

    /**
     * 是否包含错误码{errorCode}
     * @param errorCode
     * @return
     */
    public boolean includeErrorCode(String errorCode){
      return problems.stream().anyMatch(m -> m.getCode().equals(errorCode));
    }
  }

  public static boolean assertException(List<Problem> problems) {
    return problems != null && !problems.isEmpty();
  }
}
